{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "In this notebook, we are going to see how a constraint can be activated under a variable-based condition (ie, an other constraint).\n",
    "The small problem we are going to solve is the following:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "%%latex\n",
    "\\begin{equation*}\n",
    "  f(a,b)=\\begin{cases}\n",
    "    a\\times b, & \\text{if $a>b$}.\\\\\n",
    "    a+b, & \\text{otherwise}.\n",
    "  \\end{cases}\n",
    "\\end{equation*}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are three ways to handle such conditional constraint in Choco."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "%%loadFromPOM\n",
    "<repository>\n",
    "  <id>oss-sonatype-snapshots</id>\n",
    "  <url>https://oss.sonatype.org/content/repositories/snapshots/</url>\n",
    "</repository>\n",
    "<dependency>\n",
    "  <groupId>org.choco-solver</groupId>\n",
    "  <artifactId>choco-solver</artifactId>\n",
    "  <version>4.10.0</version>\n",
    "</dependency>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [],
   "source": [
    "import org.chocosolver.solver.Model;\n",
    "import org.chocosolver.solver.variables.IntVar;\n",
    "import org.chocosolver.solver.variables.BoolVar;\n",
    "import org.chocosolver.solver.Solver;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## First approach\n",
    "In the first approach, we are going to use a `ifThenElse` constraint that takes three constraints as parameters.\n",
    "The first constraint is the conditional, the second is the one that is satisfied when the condition holds, \n",
    "the third is the one that is satified when the condition doesn't hold. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model[1st try], 9 variables, 5 constraints, building time: 0,054s, w/o user-defined search strategy, w/o complementary search strategy\n",
      "A=0, B=1, H=1\n",
      "A=0, B=2, H=2\n",
      "A=0, B=3, H=3\n",
      "A=1, B=1, H=2\n",
      "A=1, B=2, H=3\n",
      "A=1, B=3, H=4\n",
      "A=2, B=3, H=5\n",
      "A=3, B=3, H=6\n",
      "A=0, B=0, H=0\n",
      "A=2, B=2, H=4\n",
      "A=1, B=0, H=0\n",
      "A=2, B=0, H=0\n",
      "A=3, B=0, H=0\n",
      "A=2, B=1, H=2\n",
      "A=3, B=1, H=3\n",
      "A=3, B=2, H=6\n",
      "Model[1st try], 16 Solutions, Resolution time 0,055s, 35 Nodes (639,2 n/s), 39 Backtracks, 0 Backjumps, 4 Fails, 0 Restarts\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "    Model model = new Model(\"1st try\");\n",
    "    IntVar A = model.intVar(\"A\", 0, 3);\n",
    "    IntVar B = model.intVar(\"B\", 0, 3);\n",
    "    IntVar F = model.intVar(\"F\", 0, 10);\n",
    "    \n",
    "    model.ifThenElse(\n",
    "        model.arithm(A,\">\",B), // if A > B\n",
    "        model.times(A, B, F), // then F = A * B\n",
    "        model.arithm(A,\"+\",B,\"=\",F)); // else F = A + B\n",
    "\n",
    "    Solver solver = model.getSolver();\n",
    "    solver.printShortFeatures();\n",
    "    while(solver.solve()){\n",
    "        System.out.printf(\"A=%d, B=%d, H=%d\\n\", A.getValue(), B.getValue(), F.getValue());\n",
    "    }\n",
    "    solver.printShortStatistics();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In that approach, some additional variables and constraints are introduced to make sure the relation holds.\n",
    "It relies on reification which attaches a boolean variable to a constraint, which is set to `true` when the constraint is satified, \n",
    "`false` otherwise (and vice versa). In this approach, the decomposition is automatically done by the model on the call to `ifThenElse`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Second approach: using expressions\n",
    "Variables can be combined together to form an expression. An expression can be arithmetical, logical or relational.\n",
    "Once declared, any expression can be turned into an integer variable (for arithmetical expression), a boolean variable (for logical expression) or a constraint (for relationnal one). \n",
    "The latter can either be posted as a combination of constraints or as a table constraint. In that case, the tuple are generated from the expression (this may not be \"free\" in term of time and space)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model[1st try], 8 variables, 6 constraints, building time: 0,012s, w/o user-defined search strategy, w/o complementary search strategy\n",
      "A=1, B=0, F=0\n",
      "A=2, B=0, F=0\n",
      "A=3, B=0, F=0\n",
      "A=2, B=1, F=2\n",
      "A=3, B=1, F=3\n",
      "A=3, B=2, F=6\n",
      "A=0, B=1, F=1\n",
      "A=1, B=1, F=2\n",
      "A=0, B=2, F=2\n",
      "A=1, B=2, F=3\n",
      "A=0, B=3, F=3\n",
      "A=1, B=3, F=4\n",
      "A=2, B=3, F=5\n",
      "A=3, B=3, F=6\n",
      "Model[1st try], 14 Solutions, Resolution time 0,023s, 29 Nodes (1 277,3 n/s), 31 Backtracks, 0 Backjumps, 2 Fails, 0 Restarts\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "    Model model = new Model(\"1st try\");\n",
    "    IntVar A = model.intVar(\"A\", 0, 3);\n",
    "    IntVar B = model.intVar(\"B\", 0, 3);\n",
    "    IntVar F = model.intVar(\"F\", 0, 10);\n",
    "    \n",
    "    F.eq( // F is equal to ...\n",
    "        A.gt(B).ift( // if A > B \n",
    "            A.mul(B), // then A * B\n",
    "            A.add(B)  // else A + B\n",
    "        )\n",
    "    ).decompose() // decompose the expression\n",
    "    //).extension() // generate a table constraint\n",
    "    .post(); // post the constraint\n",
    "\n",
    "    Solver solver = model.getSolver();\n",
    "    solver.printShortFeatures();\n",
    "    while(solver.solve()){\n",
    "        System.out.printf(\"A=%d, B=%d, F=%d\\n\", A.getValue(), B.getValue(), F.getValue());\n",
    "    }\n",
    "    solver.printShortStatistics();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this approach again, the model automatically decomposes the relation into constraints and introduces some variables. One may replace `.decompose()` by `.extension()` and note that no constraint or variable is added. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Third approach: DIY\n",
    "In that approach, constraints are reified with boolean variables and those variables are, in turn, linked to an `if then` relation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model[Model-0], 5 variables, 3 constraints, building time: 0,001s, w/o user-defined search strategy, w/o complementary search strategy\n",
      "A=1, B=0, H=0\n",
      "A=2, B=0, H=0\n",
      "A=3, B=0, H=0\n",
      "A=2, B=1, H=2\n",
      "A=3, B=1, H=3\n",
      "A=3, B=2, H=6\n",
      "A=0, B=1, H=1\n",
      "A=0, B=2, H=2\n",
      "A=0, B=3, H=3\n",
      "A=1, B=1, H=2\n",
      "A=1, B=2, H=3\n",
      "A=1, B=3, H=4\n",
      "A=2, B=3, H=5\n",
      "A=3, B=3, H=6\n",
      "Model[Model-0], 14 Solutions, Resolution time 0,034s, 29 Nodes (845,5 n/s), 31 Backtracks, 0 Backjumps, 2 Fails, 0 Restarts\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "    Model model = new Model();\n",
    "    IntVar A = model.intVar(\"A\", 0, 3);\n",
    "    IntVar B = model.intVar(\"B\", 0, 3);\n",
    "    IntVar F = model.intVar(\"F\", 0, 10);\n",
    "\n",
    "    BoolVar b = model.arithm(A, \">\", B).reify();\n",
    "    model.times(A, B, F).reifyWith(b);\n",
    "    model.arithm(A, \"+\", B, \"=\", F).reifyWith(b.not());\n",
    "\n",
    "    Solver solver = model.getSolver();\n",
    "    solver.printShortFeatures();\n",
    "    while(solver.solve()){\n",
    "        System.out.printf(\"A=%d, B=%d, H=%d\\n\", A.getValue(), B.getValue(), F.getValue());\n",
    "    }\n",
    "    solver.printShortStatistics();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "The `A>B` constraint is reified with `b` a boolean variable which is then used to reify the second constraint.\n",
    "Then, the refutation of `b` is used to reified the third constraint. \n",
    "\n",
    "When a constraint is reified with a boolean variable, it should not posted (that would forces the boolean variable to always  be equal to 1). But, the boolean variable can be used to reified other constraints and/or be linked with other variables in other constraints, like `sum` or clauses."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Java",
   "language": "java",
   "name": "java"
  },
  "language_info": {
   "codemirror_mode": "java",
   "file_extension": ".java",
   "mimetype": "text/x-java-source",
   "name": "Java",
   "pygments_lexer": "java",
   "version": "11+28"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
